/*
 * GeoVISTA Center (Penn State, Dept. of Geography)
 * Copyright (c), 2002, GeoVISTA Center
 * Licensed under Simplified BSD License
 * This library is developed and distributed for free usage,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * You can redistribute and/or modify it under the terms of the
 * Simplified BSD License
 *
 *
 *
 *
 *
 *
*
*
* @Original Author: jin Chen
* @date: Dec 10, 2004$
* @version: 1.0
*/
package edu.psu.geovista.app.visualclassifier.classify.v2;

import edu.psu.geovista.app.visualclassifier.classify.v2.control.BiColorInvertor;
import edu.psu.geovista.app.visualclassifier.classify.v2.control.ColorInvertor;
import edu.psu.geovista.app.visualclassifier.classify.v2.control.PersistenceManager;
import edu.psu.geovista.app.visualclassifier.classify.v2.control.UniColorInvertor;
import edu.psu.geovista.app.visualclassifier.classify.v2.gui.AskClassificationBoundPane;
import edu.psu.geovista.app.visualclassifier.classify.v2.gui.BasicConfigurePane;
import edu.psu.geovista.app.visualclassifier.classify.v2.gui.EsUnsupervisedSettingsGUI;
import edu.psu.geovista.app.vit.pub.utils.VitUtils;
import edu.psu.geovista.classification.*;
import edu.psu.geovista.classification.setting.ClassifySetting;
import edu.psu.geovista.classification.setting.ClassifySetting1D;
import edu.psu.geovista.classification.setting.ClassifySetting2D;
import edu.psu.geovista.colorbrewer.EsColorBrewer;
import edu.psu.geovista.common.awt.event.dataloading.AppDataEvent;
import edu.psu.geovista.common.color.BivariatePalette;
import edu.psu.geovista.common.color.Palette;
import edu.psu.geovista.common.event.BivariatePaletteEvent;
import edu.psu.geovista.common.event.BivariatePaletteListener;
import edu.psu.geovista.common.event.PaletteEvent;
import edu.psu.geovista.common.event.PaletteListener;
import edu.psu.geovista.common.utils.ArrayUtils;
import edu.psu.geovista.common.utils.collection.CollectionUtils;
import edu.psu.geovista.data.model.AppDataModel;
import edu.psu.geovista.data.model.DefaultAppDataModel;
import edu.psu.geovista.jdm.DMEngine;
import edu.psu.geovista.jdm.base.JDMUnsupervisedAlgorithm;
import edu.psu.geovista.jdm.gui.UnsupervisedSettingsGUI;
import edu.psu.geovista.legend.InteractiveLegend;
import edu.psu.geovista.ui.event.*;
import epoviz.trex.common.swing.frame.AppComponent;
import epoviz.trex.common.swing.frame.SerializableComponent;
import epoviz.trex.common.swing.layout.EvenRowSectionsPane;
import epoviz.trex.common.swing.layout.UnevenRowSectionPane;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.math.stat.descriptive.rank.Max;
import org.apache.commons.math.stat.descriptive.rank.Min;

import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.event.EventListenerList;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

public class DefaultESVisualClassifier extends JPanel
        implements ESVisualClassifier, AppComponent, SerializableComponent,
        ClassificationResultListener,ColorArrayListener,ClassBoundariesListener,
        ClassNumberListener, PaletteListener, BivariatePaletteListener,DataSetListener,
        ClassifySettingListener
{
   // private static final AspectLogger logger=AspectLogger.getLogger(DefaultESVisualClassifier.class.getName());
    final public static String RESOURCES="resources/";
    final public static String MODEL_ROOT= getClassPackage(DefaultESVisualClassifier.class );//"edu/psu/geovista/app/pcp/";
    final public static String IMAGES=MODEL_ROOT+RESOURCES+"images/";

    public static final Color COLOR_SECTION_TITLE=new java.awt.Color(71, 75, 140);
    public static final Font FONT_PAGE_TITLE = new Font("MS Sans Serif", 1, 13);
    public static final Font FONT_SECTION_TITLE = new Font("MS Sans Serif", 1, 12);

    public static final int MODE_UNI=1; // 
    public static final int MODE_BI=2;
    public static final int MODE_MULTI=3;

    private boolean working=true;//  if true, will do classification; else, do not do any classification even get settings

    //components

    protected DMEngine engine;//dataming engine
    InteractiveLegend legendPlot; // 
    EsColorBrewer colorbrewer;//=new ColorBrewerPlus();
    ColorInvertor colorInvertor;



    //control
    protected ClassificationResultListener classifyL;
    protected ColorArrayListener colorL;
    private transient EventListenerList listenerList=new EventListenerList();
    private Action[] actions;
    private PersistenceManager persistMgr;
    //GUI

    UnevenRowSectionPane mainp;
    protected EsUnsupervisedSettingsGUI dmGui;//data mining's GUI set panel
    JPanel legendSection; //only dispaly legend
    JPanel legendPane;//contain legendSection and related control components
    JPanel colorbrewerPane;
    Window window; //a window in which the VC is displayed
    BasicConfigurePane configpane;

    //properties (support functions)
    //String  persistenceRoot;// the full path of root folder where the
    Map colorInvertors;//=new HashMap(2);
    int mode=MODE_UNI;

    //data (service related)
    protected ClassificationResultItf classifyResult;
    protected Color[] dataColors;//?  
    private Map<Object,ClassifySetting> classifySettings; // 
    Map <Object,ClassificationResultItf> classifyResultsUnivar;// 
    //Map <Object, Color[]> dataColorMap;// 



    public DefaultESVisualClassifier() {
        setClassifySettings(new HashMap<Object, ClassifySetting>());
        classifyResultsUnivar =new HashMap<Object, ClassificationResultItf>();
        this.persistMgr=new PersistenceManager(this);
        initComponents();


    }
    public static String getClassPackage(Class cls){
           String bp=cls.getPackage().getName() ;
           String bpkg=bp.replace('.','/');
           bpkg=bpkg+'/';
           return bpkg;
    }




    /**
     * do init classification
     * @param dataModel
     */
    private void doInitClassification(AppDataModel dataModel) {
        //System.out.println("doInitClassification");
        Object[] dataSet = new Object[0];
        try {
            dataSet = dataModel.getRaw();
            if(dataSet==null ||dataSet.length ==0) {
                dataSet= DefaultAppDataModel.getGeoVistaRawData(dataModel) ;
            }
        } catch (Exception e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        if(dataSet.length ==0) return;
        engine.setDataset(dataSet);
        this.dmGui.setDataModel(dataModel);
       // dmGui.doClassification();//init, do it once
        this.classifyOnGUISetting();

    }

    /**
     *  
     *  
     * @param dataModel
     * @param settings
     */
    public void doAllClassification(Collection<ClassifySetting> settings){
        System.out.println("doAllClassification");
        ClassificationResultItf clsresult=null;
        for (Iterator iterator = settings.iterator(); iterator.hasNext();) {
            ClassifySetting1D sett = (ClassifySetting1D) iterator.next();
            String varname = sett.getVariableName();
            this.classifyUpdateGUI(sett);
             clsresult = this.getClassifyResult();
            classifyResultsUnivar.put(varname,clsresult);
        }
         fireAllVarsClassification();
    }


    /**
     * do classification on each of the numeric attributes independently based on a sample setting displayed in GUI
     * @param dataModel
     * @param sampleSett       
     */
     public void doAllClassification(AppDataModel dataModel,ClassifySetting1D sampleSett){
        System.out.println("doAllClassification ,ClassifySetting1D sampleSett)");
        int colcount = dataModel.getColumnCount();
        /*ClassifySetting1D sett=new ClassifySetting1D();
        sett.setClassifier(ClassifySetting.QUANTILES);
        sett.setNumOfCategory(7);
        sett.setType(ClassifySetting.TYPE_VARIABLE);
        sett.setDimensionName(ClassifySetting1D.DIMENSION_NAME_X);*/

        sampleSett.setDimensionName(ClassifySetting1D.DIMENSION_NAME_X); // to void null value
        ClassificationResultItf clsresult=null;
        for (int i=0;i<colcount ;i++){
            ClassifySetting1D sett=new ClassifySetting1D();
            try {
                BeanUtils.copyProperties(sett,sampleSett); //create a setting for each variable
            } catch (IllegalAccessException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            } catch (InvocationTargetException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
            String varname = dataModel.getColumnName(i);
            //double[] doubles = dataModel.getColumnValueAsDouble(i);
            sett.setVariableName(varname);
            this.classifyUpdateGUI(sett);
            //dmGui.setClassifySetting(sett);// so that setting is changed in GUI
            updateSetting(varname, sett);
            
        }
        //todo need also broadcast colors
        fireAllVarsClassification();

    }

    private void updateSetting(String varname, ClassifySetting1D sett) {
        ClassificationResultItf clsresult;
        this.getClassifySettings().put(varname,sett);
        clsresult = this.getClassifyResult();
        classifyResultsUnivar.put(varname,clsresult);
    }

    /**
     *  
     *  
     * @param dataModel
     * @param sampleSett
     */
    public void doGlobalAllClassification(AppDataModel dataModel,ClassifySetting1D sampleSett){
        String[] colnames = dataModel.getColumnNames();
        doMultivariateClassification(dataModel,colnames,sampleSett);
         fireAllVarsClassification();
    }


    /**
     *  
     *  
     *  
     *  
     * @param dataModel
     * @param varnames
     * @param sampleSett   
     */
     public void doMultivariateClassification(AppDataModel dataModel,String[] varnames,ClassifySetting1D sampleSett){
         AppDataModel mymodel=new DefaultAppDataModel();
         double[] mycol=new double[0];
         for (int i=0;i<varnames.length ;i++){
             int colIndex = dataModel.getColumnIndexByName(varnames[i]);
             double[] acol = dataModel.getColumnValueAsDouble(colIndex);
             /*Arrays.sort(acol);
             int index = Arrays.binarySearch(acol, 2002);
             if(index>=0){
                 System.out.println(" "+varnames[i]);
             }*/
             mycol=ArrayUtils.add(mycol,acol);
         }
        double[] minMax = getMinMax(mycol);
        System.out.println(" "+minMax);
        String dummyVarname = "Combined Column";
        mymodel.addColumn(dummyVarname,mycol);
         DMEngine dm=new DMEngine();
         sampleSett.setVariableName(dummyVarname);
         dm.setDataset(mymodel.getRaw());
         dm.doUnsupervised(sampleSett);
         ClassificationResult cr = dm.getUnsupervisedResult();
         double[] boundaryX = cr.getBoundaryX();
         for (int i=0;i<varnames.length ;i++){
            String varname = varnames[i];
            ClassifySetting1D clsett=new ClassifySetting1D(varname,sampleSett.getNumOfCategory(),ClassifySetting.SELF_DEFINED,boundaryX);
            clsett.setDimensionName(ClassifySetting1D.DIMENSION_NAME_X ); 
             this.classifyUpdateGUI(clsett);
             updateSetting(varname,clsett);
             //this.engine.doUnsupervised(clsett);//todo this.classifyUpdateGUI(sett);

        }
    }
     public static double[] getMinMax(double[] values){
        double[] v = getCopied(values);
    /*Arrays.sort(v);
    return new double[]{ v[0],v[v.length -1]};*/
        double min = new Min().evaluate(v); //values
        double max = new Max().evaluate(v);//values
        return new double[]{min,max};
    }
     private static double[] getCopied(double[] values) {
        double[] v=new double[values.length ]; // use  a copy of the data to avoid change to original data
        System.arraycopy(values,0,v,0,values.length );
        return v;
    }
    /**
     * current classify result
     * @param clsresult
     */
    private void fireAllVarsClassification() {
        ClassificationResultItf clsresult=this.getClassifyResult();
        ClassifyAllEvent e = new ClassifyAllEvent(this,clsresult);//last clsresult
        e.setClassifyResults(this.classifyResultsUnivar);
        this.fireClassificationResultChanged(e);
    }

    public boolean hasData(){
        if(this.engine.getDataset()==null||engine.getDataset().length ==0) return false;
        if(getDataModel() ==null||getDataModel().getRowCount() ==0)return false;
        return true;
    }

    public CategoryListItf getCtgList() {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    /*******************************************************************************************************
     *                classify setting
     *******************************************************************************************************/
    /**
     *  
     */
    public void saveClassifySetting(){
        ClassifySetting setting = this.dmGui.getClassifySetting();
        Map<Object, ClassifySetting> clasifySettings = this.getClassifySettings();
        if(setting instanceof ClassifySetting1D){
            ClassifySetting1D sett= (ClassifySetting1D) setting;
            String vn = sett.getVariableName();
            clasifySettings.put(vn,sett);
            //logger.log(" Save setting "+sett, VcAspect.classifySetting );
        }
        else if(setting instanceof ClassifySetting2D){
            ClassifySetting2D sett= (ClassifySetting2D) setting;
            String vnx = sett.getxVariableName();
            String vny = sett.getyVariableName();
            clasifySettings.put(vnx+"-"+vny,sett);

        }
    }

    /**
     *
     * @param varname   
     * @return
     */
    public ClassifySetting getClassifySetting(Object varname){
       return getClassifySettings().get(varname);
    }
    
    /*******************************************************************************************************
     *                services
     *******************************************************************************************************/
    /**
     * display the VC in a given window(OptionPane, Dialog, Window). If no window specified, display it in a JFrame
     */
    public void showup(){
        Window w=getWindow();
        if(w!=null){
            //w.removeAll();

            if(w instanceof JFrame){

                JFrame jfrm = (JFrame)w;
                Container cp = jfrm.getContentPane();
                cp.setLayout(new BorderLayout());
                cp.add(this);


            }
            else{
                w.setLayout(new BorderLayout() );
                w.add(this);
            }

            //w.pack();
            w.setSize(800,800);
            w.setVisible(true);
            w.show();
            //w.
            w.requestFocus();
            w.toFront();

        }
    }
    /**
     * close the window
     */
    public void close(){
        Window w=getWindow();
        if(w!=null){
            w.setVisible(false);
            w.hide();
            w.dispose();

        }
    }

    public boolean isWorking() {
        return working;
    }

    public void setWorking(boolean working) {
        this.working = working;
    }

    /**
     * broadcast the classification result to listeners
     * usually invoke upon click Apply or Ok button
     */
    public void broadcast(){
        if(isWorking()){
            //  
            // 
             SwingUtilities.invokeLater(new Runnable() {
                  public void run() {
                        fireColorArrayEvents(dataColors );
                        fireClassificationResultChanged (null);
                  }
              } );

        }
    }

    /**
     *  programatically set color scheme.
     *  Note: with different type, u have different colorscheme names
     * @param type
     * @param colorscheme
     */
    public void setUniColorScheme(String type, String colorscheme, int colorcount){
         colorbrewer.setUni(type);
         colorbrewer.setCurrentUnivariateScheme(colorscheme);
        ClassNumberEvent e=new ClassNumberEvent(new Object(),colorcount);
        classNumberChanged(e);
    }


    private void verifyColors(int[] ids, Color c){
        for (int i=0;i<ids.length ;i++){
            final int id = ids[i];
            if(dataColors[id]!=c){
                System.err.println("Found "+i +" th data's color is:"+dataColors[i]+"not not match "+c);
            }
        }
    }
    /**
     *  
     */
    public void classifyOnGUISetting(){
        if (this.isWorking() ) {
            //ClassifySetting setting = dmGui.getClassifySetting();
            // dmGui.doClassification();
            ClassifySetting settings = this.dmGui.getClassifySetting();
            if(settings!=null)
                    classify(settings);
            //broadcast();       //  
        }

    }
    /**
     *
     *  
     *  
     *  
     *
     * @param settings
     */
    public void classifyUpdateGUI(final ClassifySetting settings){
        if (this.isWorking() ) {

            dmGui.setClassifySetting(settings);

            classify(settings);

        }

    }


     /**
     *  
     *  
     * @param settings
     */
    public void classify( ClassifySetting settings){
        if (valid(settings)) {
            if(settings instanceof ClassifySetting1D){
                ClassifySetting1D sett= (ClassifySetting1D) settings;
                String classifer = sett.getClassifier();
                if(classifer.equals(ClassifySetting.UNCLASSED)){
                     //doUnclass(sett.getVariableName() );
                    return;
                }
            }
            /*URL classLocation = JavaCoreUtils.getClassLocation(DMEngine.class);
             classLocation.toString() ;*/
             //logger.log("Classify on "+settings.toString() ,VcAspect.classifySetting );
             this.engine.doUnsupervised(settings);
        }
    }
    /**
     *  
     *  
     * @param varname
     * @param broadcast
     */
    public void classifyOnVariableChanged(Object varname, boolean broadcast){
        ClassifySetting setting = getClassifySetting(varname);
        // 
        if(setting==null){//do classification using current GUI setting
          //  logger.log(" Classify on GUI setting for "+varname,  VcAspect.classifySetting);

            classifyOnGUISetting();
        }
        else{

            this.classifyUpdateGUI(setting);


        }
        if(broadcast)broadcast();
    }

    /**
     *  
     *  
     *  
     *  
     * @param sett
     */
     public void classifyOnLegendControl(final ClassifySetting sett){
         if (this.isWorking() ) {
            dmGui.setClassifySetting(sett);//pass the varname
                    ClassifySetting guisett =  dmGui.getClassifySetting();//get other info from GUI
                    /*guisett.setVariableName(vn);
                    guisett.setDimensionName(dn);*/

                    classify(guisett);
                     broadcast();
                 }

     }

   /* public void classifyGUISetting(){
         ClassifySetting settings = dmGui.getClassifySetting();
        if(settings!=null)
            classify(settings);
    }*/



    /**
     * see if the setting is valid
     * @param settings
     * @return         true if need engine to do further process
     */
    private boolean valid(ClassifySetting settings) {
        if(settings instanceof ClassifySetting1D){
            // 
            ClassifySetting1D sett = ((ClassifySetting1D) settings);


            int ctgcount = sett.getNumOfCategory();
            if(ctgcount>9){
                JOptionPane.showMessageDialog(this, " Can classify up to 9 categories. Unable to process request for "+ ctgcount+ " categories. ");
                return false;
            }

            // 
            String vn = sett.getVariableName();
           /* if(vn.equals("PopSqMi") )      {
                System.out.println(this.getClass().getName() + ".valid() varname "+vn);
                
            }*/
            if(vn!=null&&vn.equals(EsUnsupervisedSettingsGUI.CLASSIFY_ALL_ATTRIBUTES) ){
                int value = JOptionPane.showConfirmDialog(this, " Do you want to classify on all the variables with this setting? ");
                if(value==JOptionPane.OK_OPTION){
                    this.doAllClassification(this.getDataModel(),sett);
                }
                return false;//never pass the request to DMengine

            }
           /* sett.
            if()*/
           

        }


        return true;
    }

    public Color[] findDataColors(){
        if(this.dataColors!=null){
            return dataColors;
        }
        else{
            return new Color[0];
        }
    }
    public CategoryItf[] getCategories(){
        if(classifyResult!=null)
            return this.classifyResult.getCategories() ;
        else{
            return new CategoryItf[0];
        }
    }


    public static int[] getMemberIds(CategoryItf ctgs[]){
        HashSet allids=new HashSet();
        for (int i=0;i<ctgs.length ;i++){
            final CategoryItf ctg = ctgs[i];
            final HashSet memberIds = ctg.getMemberIds();
            allids.addAll(memberIds);
        }
        final int[] ids = CollectionUtils.convert2IntArray(allids);
        return ids;
    }

    public  ClassificationResultItf getClassificationResultUnivar(Object varname){
        return this.classifyResultsUnivar.get(varname);
    }


    /*******************************************************************************************************
     *                supports
     *******************************************************************************************************/


    /**
     *
     * @return
     */
    private ClassifySetting getCurrentSetting(){
        return this.dmGui.getClassifySetting();
    }

    /*******************************************************************************************************
     *                Init & GUI
     *******************************************************************************************************/
    private void initComponents() {
        colorInvertors=new HashMap(2);
        BiColorInvertor bv=new BiColorInvertor(this);
        UniColorInvertor uv=new UniColorInvertor(this);
        colorInvertors.put(new Integer(MODE_BI),bv);
        colorInvertors.put(new Integer(MODE_UNI),uv);

        //legend
        legendPlot =new InteractiveLegend();
        //colorbrewer
        colorbrewer=new EsColorBrewer();
        //colorbrewerPane=new JPanel();
        //dm
        dmGui = new EsUnsupervisedSettingsGUI();
        dmGui.setVc(this);
        engine = new DMEngine();
        setActions();//must called before calling setGUI()
        setGUI();
        //Setup events
        setupEventRelations();




    }
    /**
     * set actions for buttons on control pane's
     */
    private void setActions(){
        Action apply=new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                saveBroadcast();
            }
        };
        apply.putValue(Action.NAME ,"Apply");


        Action ok=new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                 saveBroadcast();
                //broadcast();
                close();
            }
        };
        ok.putValue(Action.NAME ,"OK");

        Action cancel=new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                close();
            }
        };
        cancel.putValue(Action.NAME ,"Cancel");

        this.actions = new Action[]{ok,cancel,apply};
    }

    private void saveBroadcast() {
        String[] attnames = this.classifyResult.getAttNames();
        String attrname = attnames[0];
        this.classifyResultsUnivar.put(attrname,classifyResult);// store this modified classifyResult.
        try {
            this.save();
        } catch (Exception e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        broadcast();
    }
    /*******************************************************************************************************
     *                events
     *******************************************************************************************************/
    /**
     *
     * @param l
     */
    public void addColorArrayListener (ColorArrayListener l) {

        // if(! contains(listenerList, l)) // 
        this.listenerList.add(ColorArrayListener.class ,l);


    }
    public void removeColorArrayListener (ColorArrayListener l) {
        this.listenerList.remove(ColorArrayListener.class ,l);
    }

    // 
    /*public static boolean contains(EventListenerList listenerList,Object o){
         Object[] ls = listenerList.getListenerList();
        Object[] listeners=(Object[]) ls.clone() ;
        if(listeners!=null&&listeners.length >0){
            for (int i=0;i<listeners.length ;i++){
                Object element=listeners[i];
                if(element==o ) {//should not use equals
                    return true;
                }

            }
            return false;
        }
        else
            return false;
    }*/
    private void fireColorArrayEvents(Color[] colors){
        Object[] listeners=this.listenerList.getListenerList() ;
        ColorArrayEvent e=null;
        for (int i=listeners.length -2;i>=0;i-=2){
            if (listeners[i]==ColorArrayListener.class){
                e=new ColorArrayEvent(this,colors);
                ((ColorArrayListener)listeners[i+1]).colorArrayChanged(e);
            }


        }
    }
    /**
     * adds an ClassificationResultListener
     */
    public void addClassificationResultListener (ClassificationResultListener l) {
        listenerList.add(ClassificationResultListener.class, l);
    }
    /**
     * removes an ClassificationResultListener from the component
     */
    public void removeClassificationResultListener (ClassificationResultListener l) {
        listenerList.remove(ClassificationResultListener.class, l);

    }

    private void fireClassificationResultChanged(ClassificationResultEvent e) {
        //System.out.println("in fire classification result changed");
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        //ClassificationResultEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == ClassificationResultListener.class) {
                // Lazily create the event:
                if (e == null) {
                    e = new ClassificationResultEvent(this, (ClassificationResult)this.classifyResult );
                }
                ((ClassificationResultListener)listeners[i + 1]).classificationResultChanged(e);
            }
        }//next i

    }


    protected void setupEventRelations() {
        dmGui.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                /*JDMUnsupervisedTask task = dmGui.getUnsupervisedTask();
                engine.setUnsupervisedTask(task);*/
                ClassifySetting settings = dmGui.getClassifySetting();
                if(settings!=null)
                    classify(settings);
                    //engine.doUnsupervised(settings);
            }
        });
        //engine.addClassificationResultListener(legend);
        engine.addClassificationResultListener(this);

        this.legendPlot.addColorArrayListener(this);
        this.legendPlot.addClassBoundariesListener(this);
        this.legendPlot.addClassNumberListener(this);

        colorbrewer.addBivariatePaletteListener(this);
        colorbrewer.addPaletteListener(this);


    }
    /**
     * from engine to legend
     * fired when classification is generated
     * @param e
     */
    public void classificationResultChanged(ClassificationResultEvent e) {
        classifyResult = (ClassificationResultItf) e.getClassificationResult();
        if(classifyResult!=null){
       // int xclass = classifyResult.getNumberOfClassX();
        int yclass = classifyResult.getNumberOfClassY();
        // 
        if(yclass<=0){//no y class --> uni classification
            this.setMode(MODE_UNI);
        }
        else{//bi classification
            this.setMode(MODE_BI);
        }


        // classifyResult.
        //initialize new colors
        final int rowCount = classifyResult.getRowCount();//# of member ID
        Color[] colors=new Color[rowCount];
        Arrays.fill(colors,new Color(255,0,0) );//make sure every color elements has value
        this.setDataColors(colors);



        this.legendPlot.classificationResultChanged(e);
        this.legendPane.repaint();//After legend changed, need repaint to update the  graph
        }
        else{
            System.err.println(this.getClass().getName() + "classificationResultChanged() get null ClassificationResultItf");
        }
        
    }
    public void classifySettingChanged(ClassifySettingEvent event) {


        ClassifySetting setting = event.getSetting();

        this.classifyUpdateGUI(setting);

    }


    /**
     * from legend to engine
     * fired when user manually change the classification boundary
     * @param e
     */
    public void classBoundariesChanged(ClassBoundariesEvent e) {
        /*   
        e.setClassifyResult(this.classifyResult );// 
        this.engine.classBoundariesChanged(e);*/
        /**
         *  
         */
        double[] classBoundaries = e.getClassBoundaries();
        ClassifySetting sett = this.dmGui.getClassifySetting();
        if(sett instanceof ClassifySetting1D){
            ClassifySetting1D setting = (ClassifySetting1D) sett;
            setting.setBoundaries(classBoundaries);
            setting.setClassifier(JDMUnsupervisedAlgorithm.SELF_DEFINED);
            //this.doClassification(setting);//update GUI
            //engine.doUnsupervised(setting);
        }
        else if(sett instanceof ClassifySetting2D){
            ClassifySetting2D setting = (ClassifySetting2D) sett;
            if(classBoundaries!=null&&classBoundaries.length >0){
                setting.setBoundX(classBoundaries);
                setting.setxClassifier(JDMUnsupervisedAlgorithm.SELF_DEFINED);
            }
            double[] classBoundariesY = e.getClassBoundariesY();
            if(classBoundariesY!=null&&classBoundariesY.length >0){
                setting.setBoundY(classBoundariesY);
                setting.setyClassifier(JDMUnsupervisedAlgorithm.SELF_DEFINED);
            }
            //engine.doUnsupervised(setting);
        }
        this.classifyUpdateGUI(sett);//update GUI
    }
    /**
     * from engine GUI --> legend --> colorbrewer
     * fired when GUI change # of classification
     * @param e
     */
    public void classNumberChanged(ClassNumberEvent e) {
        this.colorbrewer.classNumberChanged(e);
    }
    /**
     * from colorbrewer to legend
     * fired when ui-var color is generated on colorbrewers
     * @param e
     */
    public void paletteChanged(PaletteEvent e) {
        //this.legend.paletteChanged(e);
        //this.legendPane.repaint();//After legend changed, need repaint to update the  graph

        ClassificationResultItf cr= this.classifyResult;
        if(cr==null)return;
        final int numX = cr.getNumberOfClassX();//number of categories in X
        final Palette pal = e.getPalette();
        // 
        final Color[] ucolors = pal.getColors(numX);
        if(this.colorInvertor instanceof UniColorInvertor){
            UniColorInvertor invt = ((UniColorInvertor)colorInvertor);
            invt.setUniColors(ucolors);
        }
        //assign colors to categories
        displayColors(ucolors);

    }
    /**
     *  
     * @param ucolors
     */
    public  void displayColors(final Color[] ucolors) {
        Collection<ClassificationResultItf> classificationResultItfs = this.classifyResultsUnivar.values();
        for (Iterator iterator = classificationResultItfs.iterator(); iterator.hasNext();) {
            ClassificationResultItf cr = (ClassificationResultItf) iterator.next();
            updateColorForCategories(ucolors,cr);
        }
        updateColorForCategories(ucolors,this.classifyResult);
        
        this.legendPlot.setColors(ucolors);
        legendPane.repaint();

        this.fireAllVarsClassification();
        //this.fire
    }

    private void updateColorForCategories(final Color[] ucolors,ClassificationResultItf classifyResult) {
        for (int i=0;i<ucolors.length ;i++){
            try {
                CategoryItf ctg = classifyResult.getCategory(i);
                if(ctg!=null)  {
                    final Color color = ucolors[i];
                    ctg.setVisualInfo(new BasicVisualInfo(color));//.setColor();
                    updateColorForCategory(ctg,color);
                }
            } catch (Exception e1) {
                e1.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
        }
    }


    /**
     * from colorbrewer to legend
     * fired when bi-var color is generated on colorbrewers
     * @param e
     */
    public void bivariatepaletteChanged(BivariatePaletteEvent e) {
        /*this.legend.bivariatepaletteChanged(e);
        this.legendPane.repaint();//After legend changed, need repaint to update the  graph*/
        //assign colors to categories
        ClassificationResultItf cr= this.classifyResult;
        final int numX = cr.getNumberOfClassX();//number of categories in X
        final int numY = cr.getNumberOfClassY();
        BivariatePalette pal = e.getPalette();
        final Color[][] bicolors = pal.getColors(numX, numY);
        if(this.colorInvertor instanceof BiColorInvertor){
            BiColorInvertor invt = ((BiColorInvertor)colorInvertor);
            invt.setBiColors(bicolors);
        }
        displayColors(bicolors);

    }

    public void displayColors(final Color[][] bicolors) {
        updateColorForCategories(bicolors);

        this.legendPlot.setBiColors(bicolors);
        this.legendPane.repaint();
    }

    /**
     * fill categories with the colors and update dataColors and
     * @param bicolors
     */
    private void updateColorForCategories(Color[][] bicolors) {
        ClassificationResultItf cr= this.classifyResult;
        //Color[] datacolor=(Color[]) this.getDataColors().clone();
        for (int i=0;i<bicolors.length ;i++){
            Color[] rowcolor=bicolors[i];
            for (int j=0;j<rowcolor.length ;j++){
                Color color = rowcolor[j];
                try {

                    Category2D ctg = cr.getCategory(i,j);
                    if(ctg!=null){
                        ctg.setVisualInfo(new BasicVisualInfo(color));
                        updateColorForCategory(ctg,color);
                    }


                } catch (Exception e1) {
                    e1.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                }
            }  //for2
        }//for1
    }
    /**
     * generate dataColors
     * @param ctg
     * @param color
     */
    private void updateColorForCategory(CategoryItf ctg, Color color){
        final HashSet memberIds = ctg.getMemberIds();
       // ctg.setVisualInfo(new BasicVisualInfo(color));
        final int[] ids = CollectionUtils.convert2IntArray(memberIds);
        final Color[] dataColors = this.getDataColors() ;

        for (int i=0;i<ids.length ;i++){
            int id=ids[i];     //record ID
            dataColors[id]=color;

        }

    }

    
    /**
     * from legend to external clients
     * fired when finally colors is generated for classifications
     * @param e
     */
    public void colorArrayChanged(ColorArrayEvent e) {
        //todo color should be generated by categories
        //dataColors = e.getColors();

        //new JLabel().setOpaque();
    }



    public void dataSetChanged(DataSetEvent e) {
        edu.psu.geovista.data.model.AppDataModel tmpModel;
        if(e instanceof AppDataEvent){
            AppDataEvent evt=(AppDataEvent) e;
            tmpModel=evt.getDataModel();
        }
        else{
            Object[] dataSet = e.getDataSet();
            tmpModel=DefaultAppDataModel.createDefaultAppDataModel(dataSet);
        }
        this.setDataModel(tmpModel);
    }

    public void setDataModel(AppDataModel dataModel) {
        AppDataModel mymodel = this.getDataModel();
        if(mymodel ==null||!mymodel.equals(dataModel) ){
            doInitClassification(dataModel);
            this.broadcast(); //init, fire color event to all other components
        }
    }
    /*******************************************************************************************************
     *                persistence
     *******************************************************************************************************/
    public void load() throws Exception {
       persistMgr.load();
       // this.dmGui.set
    }

    public void save() throws Exception {

        this.persistMgr.save();
    }

    /*******************************************************************************************************
     *                properties
     *******************************************************************************************************/
    /**
     *
     * @return
     */
    public Window getWindow() {
        if(window==null){
            window=new JFrame("Visual Classifier");
            window.setSize(800,640);
        }
        return window;
    }

    public void setWindow(Window window) {
        this.window = window;
    }


    public InteractiveLegend getLegendPlot() {
        return legendPlot;
    }

    public Color[] getDataColors() {
        return dataColors;
    }
    public static Map<Object, Color[]> getDataColorMap(Map <Object,ClassificationResultItf> classifyResults, int rowcount){
        return BasicClassificationResult.getDataColorMap(classifyResults,rowcount);
    }
    public void setDataColors(Color[] dataColors) {
        this.dataColors = dataColors;
    }

    public int getMode() {
        return mode;
    }

    public void setMode(int mode) {
        this.mode = mode;
        ColorInvertor invt = (ColorInvertor) this.colorInvertors.get(new Integer(mode));
        if(invt!=null){
            this.setColorInvertor(invt);
        }

    }

    public ColorInvertor getColorInvertor() {
        return colorInvertor;
    }

    public void setColorInvertor(ColorInvertor colorInvertor) {
        this.colorInvertor = colorInvertor;
    }
    public ClassificationResultItf getClassifyResult() {
        return classifyResult;
    }
     public AppDataModel getDataModel() {
        if(dmGui==null)return null;
        return this.dmGui.getDataModel() ;
    }


    public void setPersistenceRoot(String root) {
        this.persistMgr.setRoot(root);
    }
     public Map<Object, ClassifySetting> getClassifySettings() {
        return classifySettings;
    }

    /**
     *  
     * @param classifySettings
     */
    public void setClassifySettings(Map<Object, ClassifySetting> classifySettings) {
        this.classifySettings = classifySettings;
       /* if(dmGui!=null)  {
         //dmGui.selectVariable(2);
         //this.broadcast();
            fireAllVarsClassification();
        }*/
        AppDataModel dataModel = this.getDataModel();
        if(dataModel!=null){
        Collection<ClassifySetting> settings = classifySettings.values();
        this.doAllClassification(settings);
        }
    }
    private JButton getDoGlobalSettBtn(){
        JButton btn=new JButton("Global");
                    btn.addActionListener(new ActionListener(){
                            public void actionPerformed(ActionEvent e){
                                AppDataModel dm = dmGui.getDataModel();
                                ClassifySetting setting = dmGui.getClassifySetting();
                                if(setting instanceof ClassifySetting1D ){
                                    ClassifySetting1D sett= (ClassifySetting1D) setting;
                                    //ClassifySetting1D mysett=new ClassifySetting1D("",sett.getNumOfCategory(),sett.getClassifier(),null);
                                    DefaultESVisualClassifier.this.doGlobalAllClassification(dm,sett);
                                }
                            }
                    });
        return btn;
    }
    private void askSetting(){
       // Object varname = this.uniAttrSel.getSelectedItem();
        ClassifySetting setting = dmGui.getClassifySetting();
        if(setting instanceof ClassifySetting1D){
            setvalues1D(setting);
        }
        else if( setting instanceof ClassifySetting2D){
            setvalues2D(setting);
        }


        
    }
    private void setvalues2D(ClassifySetting setting) {
       ClassifySetting2D sets= (ClassifySetting2D) setting;
        double[] boundX = sets.getBoundX();//.getBoundaries();
        if(boundX==null) boundX=this.getClassifyResult().getBoundaryX();
        double[] boundY= sets.getBoundY();//.getBoundaries();
        if(boundY==null) boundY=this.getClassifyResult().getBoundaryY();
        AskClassificationBoundPane askXp=new AskClassificationBoundPane(boundX);
        AskClassificationBoundPane askYp=new AskClassificationBoundPane(boundY);

        String varnameX = sets.getxVariableName();
        String varnameY = sets.getyVariableName();
        askXp.setBorder(new TitledBorder(" "+varnameX));
        askYp.setBorder(new TitledBorder(" "+varnameY));
        EvenRowSectionsPane askP=new EvenRowSectionsPane(1 ,2);
        askP.clearControlPane();
        askP.addToMainPane(askXp);
        askP.addToMainPane(askYp);
        int v = VitUtils.showDialog(this, askP, "Set boundary for categories ", null);
        if(v==JOptionPane.OK_OPTION){
            double[] mybdX = askXp.getBound();
            double[] mybdY = askYp.getBound();
            sets.setBoundX(mybdX);
            sets.setBoundY(mybdY);
           // sets. setClassifier(ClassifySetting.SELF_DEFINED);
            this.classifyUpdateGUI(sets);
        }

    }
    private void setvalues1D(ClassifySetting setting) {
        ClassifySetting1D sets= (ClassifySetting1D) setting;
        double[] bounds = sets.getBoundaries();
        if(bounds==null) bounds=this.getClassifyResult().getBoundaryX();
        AskClassificationBoundPane askp=new AskClassificationBoundPane(bounds);
        int v = VitUtils.showDialog(this, askp, "Set boundary for categories ", null);
        if(v==JOptionPane.OK_OPTION){
            double[] mybd = askp.getBound();
            sets.setBoundaries(mybd);
            sets.setClassifier(ClassifySetting.SELF_DEFINED);
            this.classifyUpdateGUI(sets);
        }

    }

    /*******************************************************************************************************
     *                GUI
     *******************************************************************************************************/


    protected void setGUI(){
        mainp=new UnevenRowSectionPane();
        mainp.removeControlPane();
        legendSection=new JPanel();
        legendPane=new JPanel();
        this.configpane =new BasicConfigurePane();
        this.configpane.setActions(this.actions );

        //setting panel
        JPanel typePanel = dmGui.getTypePanel();
        JPanel paraPanel = dmGui.getParametersPanel();

        FlowLayout fl=new FlowLayout(FlowLayout.RIGHT );
        JPanel controlPane=new JPanel(fl);
        //controlPane.setBorder(BorderFactory.createLineBorder(Color.gray ) );
        controlPane.add(getDoGlobalSettBtn());
        JPanel settingSection=new JPanel(new BorderLayout());
        settingSection.add(paraPanel);
        settingSection.add(controlPane, BorderLayout.SOUTH);


        dmGui.addClassifyTypeChangeListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                String actcmd = evt.getActionCommand();
                //if(actcmd.e)
                showLegend(actcmd) ;
            }
        });




        //legend panel
        JPanel biLegend = legendPlot.getBiPanel();
        JPanel uniLegend = legendPlot.getUniPanel();
        JPanel legendSection=new JPanel();
        legendSection.setLayout(new CardLayout());
        legendSection.add(uniLegend,UnsupervisedSettingsGUI.uniString);
        legendSection.add(biLegend,UnsupervisedSettingsGUI.biString );
        legendSection.add(new JPanel(),UnsupervisedSettingsGUI.multiString);
        this.legendSection=legendSection;

        legendPane.setBorder(BorderFactory.createCompoundBorder(
                createTitledBorder( "3. Adjust legend and set colors"),
                BorderFactory.createEmptyBorder(5,5,5,5)));
        JPanel legendControl=new JPanel();
        //legendControl.setBorder(BorderFactory.createLineBorder(Color.BLACK ) );
        JButton setColorBtn=new JButton("  set colors   ");
        setColorBtn.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){
                //JOptionPane.showMessageDialog(ESVisualClassifier.this, colorbrewerPane);
                VitUtils.showDialog(DefaultESVisualClassifier.this, colorbrewerPane,"Please choose a color scheme",null);
            }
        });

        JButton invertColorBtn=new JButton("Invert colors");
        invertColorBtn.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){
                colorInvertor.invertColor();
            }
        });

        JButton setValuesBtn=new JButton("  set values  ");
        setValuesBtn.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){
                //System.out.println("set values");
                askSetting();
            }
        });
        
       JButton saveSettingBtn =new JButton("save setting");
        saveSettingBtn.setToolTipText(" save current classify setting ");
                    saveSettingBtn.addActionListener(new ActionListener(){
                            public void actionPerformed(ActionEvent e){
                                saveClassifySetting();
                            }
                    });
        Dimension btnsize=new Dimension(100,30);
        setColorBtn.setPreferredSize(btnsize);
        invertColorBtn.setPreferredSize(btnsize);
        JToolBar tbr=new JToolBar(JToolBar.VERTICAL);
        //tbr.setLayout(new FlowLayout());
        tbr.setFloatable(false);
        tbr.add(setColorBtn) ;
        tbr.add(invertColorBtn);
        tbr.add(setValuesBtn);
        tbr.add(saveSettingBtn);
        
        legendControl.add(tbr);
        //legendControl.add(setColorBtn);
        //legendControl.add(invertColorBtn,FlowLayout.);
        legendPane.setLayout(new BorderLayout() );
        legendPane.add(legendSection);
        legendPane.add(legendControl,BorderLayout.EAST );


        //colorbrewer pane
        JPanel cbp=new JPanel();
        cbp.setLayout(new CardLayout());
        cbp.add(colorbrewer.getUniPanel(),UnsupervisedSettingsGUI.uniString);
        cbp.add(colorbrewer.getBiPanel(),UnsupervisedSettingsGUI.biString );
        cbp.add(new JPanel(),UnsupervisedSettingsGUI.multiString);
        colorbrewerPane=cbp;

        JComponent comps[]=new JComponent[]{typePanel,settingSection,legendPane};
        double [] weights=new double[]{1,2,6};
        this.mainp.addToMainPane(comps,weights);

        configpane.setMainP(mainp);

        this.setLayout(new BorderLayout());
        this.add(configpane);



    }

    public  static TitledBorder createTitledBorder(String s){
        TitledBorder titledBorder = BorderFactory.createTitledBorder(null, s,
                TitledBorder.DEFAULT_JUSTIFICATION,
                TitledBorder.DEFAULT_POSITION,
                FONT_PAGE_TITLE,COLOR_SECTION_TITLE );
        return titledBorder;
    }
    private void showLegend(String legendName) {
        CardLayout legendLy = (CardLayout)(legendSection.getLayout());
        legendLy.show(legendSection, legendName);

        CardLayout colorLy = (CardLayout)(colorbrewerPane.getLayout());
        colorLy.show(colorbrewerPane, legendName);
    }







}
